/***
 * JAVANAISE Implementation
 * JvnServerImpl class
 * Contact: 
 *
 * Authors: 
 */

package jvn.coord;

import java.io.Serializable;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import jvn.client.JvnException;
import jvn.client.interfaces.JvnObjectI;
import jvn.client.interfaces.JvnRemoteServerI;
import jvn.coord.interfaces.JvnRemoteCoordI;

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;

public class JvnCoordImpl extends UnicastRemoteObject implements JvnRemoteCoordI {

	private static final long serialVersionUID = 1L;

	private static String nomService = "coordJavanaise";

	private List<DistributedObject> distributedObjects = Collections
			.synchronizedList(new LinkedList<DistributedObject>());

	private Map<JvnRemoteServerI, Set<Integer>> jvnRemoteServerAssociateObjects = Collections
			.synchronizedMap(new HashMap<JvnRemoteServerI, Set<Integer>>());

	final static private Logger logger = Logger.getLogger(JvnCoordImpl.class);

	public static void main(String args[]) {

		BasicConfigurator.configure();
		try {
			new JvnCoordImpl();
		} catch (Exception e) {
			logger.error("Warning jvnCoordImpl is already launch (or the port 1099 is busy).");
			System.exit(0);
		}
	}

	/**
	 * Default constructor
	 * 
	 * @throws JvnException
	 **/
	private JvnCoordImpl() throws Exception {

		Registry registry = LocateRegistry.createRegistry(1099);
		// JvnRemoteCoordI stub = (JvnRemoteCoordI) UnicastRemoteObject.exportObject(this, 0);
		registry.bind(JvnCoordImpl.nomService, this);
	}

	private Lock lockjvnGetObjectId = new ReentrantLock();

	/**
	 * Allocate a NEW JVN object id (usually allocated to a newly created JVN object)
	 * 
	 * @throws java.rmi.RemoteException
	 *             ,JvnException
	 **/
	@Override
	public int jvnGetObjectId() throws java.rmi.RemoteException, jvn.client.JvnException {
		int i;
		try {
			lockjvnGetObjectId.lock();
			this.distributedObjects.add(null);
			i = this.distributedObjects.size() - 1;
		} finally {
			lockjvnGetObjectId.unlock();
		}
		logger.debug("Demande d'un nouvel id ->" + i);
		return i;
	}

	/**
	 * Associate a symbolic name with a JVN object.<br>
	 * And give it a write lock on it.
	 * 
	 * @param jon
	 *            : the JVN object name
	 * @param jo
	 *            : the JVN object
	 * @param js
	 *            : the remote reference of the JVNServer
	 * @throws java.rmi.RemoteException
	 *             ,JvnException
	 **/
	@Override
	public void jvnRegisterObject(String jon, JvnObjectI jo, JvnRemoteServerI js) throws java.rmi.RemoteException,
			jvn.client.JvnException {

		DistributedObject distributedObject = new DistributedObject();
		distributedObject.setName(jon);
		distributedObject.setObject(jo);
		this.distributedObjects.set(jo.jvnGetObjectId(), distributedObject);
		this.jvnLockWrite(jo.jvnGetObjectId(), js);

		// this.distributedObjects.get(jo.jvnGetObjectId());
		// distributedObject.setName(jon);
		// FIXME utilisation de JvnRemoteServerI js ?
		logger.debug("Demande d'enregistrement d'un objet nommé " + jon);
	}

	/**
	 * Get the reference of a JVN object managed by a given JVN server
	 * 
	 * @param jon
	 *            : the JVN object name
	 * @param js
	 *            : the remote reference of the JVNServer
	 * @throws java.rmi.RemoteException
	 *             ,JvnException
	 * @see JvnRemoteCoordI#jvnLookupObject(String, JvnRemoteServerI)
	 **/
	public synchronized JvnObjectI jvnLookupObject(String jon, JvnRemoteServerI js) throws java.rmi.RemoteException,
			jvn.client.JvnException {

		DistributedObject distributedObject = null;
		DistributedObject distributedObjectSearch;
		if (jon == null) {
			logger.warn("Attention, un nom nul à été passé à jvnLookupObject");
		}
		int joi = -1;
		for (int i = distributedObjects.size() - 1; i >= 0; i--) {

			distributedObjectSearch = distributedObjects.get(i);

			if (distributedObjectSearch != null && jon.equals(distributedObjectSearch.getName())) {
				distributedObject = distributedObjectSearch;
				joi = i;
				break;
			}
		}
		if (distributedObject == null) {
			logger.info("L'objet n'a pas été trouvé, ou il n'a pas encore été enegistré.");
			return null;
		}
		// on sait que joi != -1
		addJoiToJs(joi, js);
		logger.debug("Demande de la référence d'un objet nommé " + jon + "[" + joi + "]");
		return distributedObject.getObject();
	}

	/**
	 * Get a Read lock on a JVN object managed by a given JVN server
	 * 
	 * @param joi
	 *            : the JVN object identification
	 * @param js
	 *            <DistributedObject> : the remote reference of the server
	 * @return the current JVN object state
	 * @throws java.rmi.RemoteException
	 *             , JvnException
	 **/
	public Serializable jvnLockRead(int joi, JvnRemoteServerI js) throws java.rmi.RemoteException, JvnException {
		DistributedObject distributedObject = this.distributedObjects.get(joi);
		logger.debug("LockRead : demande de verrou sur un objet nommé " + distributedObject.getName() + "[" + joi + "]");
		synchronized (this) {
			distributedObject.lockRead(js);
		}
		logger.debug("LockRead : verrou obtenu sur un objet nommé " + distributedObject.getName() + "[" + joi + "]");
		return distributedObject.getObject().jvnGetObjectState();
	}

	/**
	 * Get a Write lock on a JVN object managed by a given JVN server
	 * 
	 * @param joi
	 *            : the JVN object identification
	 * @param js
	 *            : the remote reference of the server
	 * @return the current JVN object state
	 * @throws java.rmi.RemoteException
	 *             , JvnException
	 **/
	public Serializable jvnLockWrite(int joi, JvnRemoteServerI js) throws java.rmi.RemoteException, JvnException {
		DistributedObject distributedObject = this.distributedObjects.get(joi);
		if (distributedObject == null) {
			logger.warn("L'objet n'a pas été trouvé");
		}

		synchronized (this) {
			distributedObject.lockWrite(js);
		}
		logger.debug("Demande de lockWrite un objet " + distributedObject.getName() + "[" + joi + "] par " + js);

		return distributedObject.getObject().jvnGetObjectState();
	}

	/**
	 * A JVN server terminates
	 * 
	 * @param js
	 *            : the remote reference of the server
	 * @throws java.rmi.RemoteException
	 *             , JvnException
	 **/
	public synchronized void jvnTerminate(JvnRemoteServerI js) throws java.rmi.RemoteException, JvnException {

		if (this.jvnRemoteServerAssociateObjects.get(js) != null) {
			for (int joi : this.jvnRemoteServerAssociateObjects.get(js)) {
				this.distributedObjects.get(joi).unregister(js);
			}
		}
		logger.debug("Terminaison de " + js);

	}

	private synchronized void addJoiToJs(int joi, JvnRemoteServerI js) {
		Set<Integer> associatedObjects = this.jvnRemoteServerAssociateObjects.get(joi);
		if (associatedObjects == null) {
			associatedObjects = new HashSet<Integer>();
		}
		associatedObjects.add(joi);
	}

	@Override
	public void reinitialize() throws RemoteException, JvnException {
		distributedObjects.clear();
		jvnRemoteServerAssociateObjects.clear();
	}
}
